package com.geek.gkmember.framework.dispatch;

import com.geek.gkmember.framework.dispatch.annotation.Dispatch;
import org.apache.commons.collections.CollectionUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <h1>分派查找者。</h1><br/>
 *
 * <h1><br/><a href="https://u.geekbang.org/subject/arch3/100614201">Java 业务架构实战营：https://u.geekbang.org/subject/arch3/100614201</a></h1><br/><h1><a href="https://gitee.com/ylimhhmily/BizArchPracticeCamp">Java 业务架构实战营代码地址：https://gitee.com/ylimhhmily/BizArchPracticeCamp</a></h1><br/><h1><a href="https://u.geekbang.org/subject/java4th/1001148?source=app_share">高级 Java 工程体系课 2023 版：https://u.geekbang.org/subject/java4th/1001148?source=app_share</a></h1><br/><h1><a href="https://time.geekbang.org/column/intro/100312101?tab=catalog">Dubbo 源码剖析与实战：https://time.geekbang.org/column/intro/100312101?tab=catalog</a></h1>
 *
 * @author hmilyylimh
 *         ^_^
 * @version 0.0.1
 *         ^_^
 * @date 2022-03-19
 *
 */
public class DispatchFinder {

    /** <h2> key = 某一类功能的接口，value = 符合某一类功能接口的所有集合</h2> **/
    private static final Map<Class<?>, Map<String, Object>> SERVICE_CACHE = new HashMap<>();

    /**
     * <h2>从符合 interfaceClass 接口的所有实现类中寻找 dispatchKey 对应的实现类。</h2>
     *
     * @param dispatchKey: 为 {@linkplain Dispatch#value()} 的值；
     * @param interfaceClass：某一类功能的接口；
     * @param <T>
     * @return
     */
    public static <T> T find(String dispatchKey, Class<T> interfaceClass) {
        // 先从缓存中看看能否查找到 dispatchKey 对应的实现类
        Map<String, Object> dispatchGroup = SERVICE_CACHE.get(interfaceClass);
        if (dispatchGroup != null) {
            return (T) dispatchGroup.get(dispatchKey);
        }

        // 没有缓存的话，则通过加锁的方式从 Spring 的容器中加载
        synchronized (SERVICE_CACHE) {
            // 再次从缓存中获取
            dispatchGroup = SERVICE_CACHE.get(interfaceClass);
            if (dispatchGroup != null) {
                return (T) dispatchGroup.get(dispatchKey);
            }

            // 从 Spring 的容器中加载
            init(interfaceClass);
        }

        // 如果还是没有查找到的话，则异常抛出
        dispatchGroup = SERVICE_CACHE.get(interfaceClass);
        if (dispatchGroup == null) {
            throw new RuntimeException(dispatchKey + " 没有分派到对应的实现类, interfaceClass=" + interfaceClass.getName());
        }
        return (T) dispatchGroup.get(dispatchKey);
    }

    private static <T> void init(Class<T> interfaceClass) {
        Map<String, T> beanGroup = SpringCtxUtils.getBeansOfType(interfaceClass);
        Map<String, Object> dispatchGroup = new HashMap<>((beanGroup.size() << 1));

        List<String> conflictList = new ArrayList<>();
        for (Map.Entry<String, T> entry : beanGroup.entrySet()) {
            // bean 为 interfaceClass 接口的其中一个实现类
            T bean = entry.getValue();
            Class<?> beanClass = bean.getClass();
            Dispatch dispatchAnno = beanClass.getAnnotation(Dispatch.class);
            if (dispatchAnno == null) {
                // 如果找不到注解的话，则可能该 bean 是被代理过的
                Object sourceTarget = ProxyUtils.getTarget(bean);
                dispatchAnno = sourceTarget.getClass().getAnnotation(Dispatch.class);
                if (dispatchAnno == null) {
                    continue;
                }
            }

            // 冲突检测如果没什么问题的话，就将对象赋值到 dispatchGroup 中
            String[] dispatchKeyGroup = dispatchAnno.value();
            for (String dispatchKey : dispatchKeyGroup) {
                groupCheck(dispatchAnno.group(), dispatchKey);
                conflictCheck(conflictList, dispatchGroup.get(dispatchKey), beanClass);

                dispatchGroup.put(dispatchKey, bean);
            }
        }

        // 重名检测
        if (CollectionUtils.isNotEmpty(conflictList)) {
            throw new RuntimeException("interface=" + interfaceClass.getName()
                    + " 下多个实例的Enum重名, 重名的集合为: " + String.join(",", conflictList));
        }

        // 最终向 SERVICE_CACHE 进行赋值
        SERVICE_CACHE.put(interfaceClass, dispatchGroup);
    }

    /**
     * 校验枚举名字，防止分组混乱和拼写错误
     *
     * @param group
     * @param dispatchKey
     */
    private static void groupCheck(Class<? extends Enum> group, String dispatchKey) {
        Enum.valueOf(group, dispatchKey);
    }

    private static void conflictCheck(List<String> conflictList, Object oldBean, Class<?> beanClass) {
        if (oldBean == null) {
            return;
        }

        conflictList.add(String.join(" conflict with ", oldBean.getClass().getName(), beanClass.getName()));
    }
}